using System;

using AGG.Transform;
using AGG.UI;
using AGG.Image;
using AGG.VertexSource;
using AGG.RasterizerScanline;

namespace AGG
{
    public struct color_function_profile : IColorFunction
    {
        public color_function_profile(RGBA_Bytes[] colors, byte[] profile)
        {
            m_colors = colors;
            m_profile = profile;
        }

        public int size() { return 256; }
        public RGBA_Bytes this[int v]
        { 
            get
            {
                return m_colors[m_profile[v]]; 
            }
        }

        RGBA_Bytes[] m_colors;
        byte[] m_profile;
    };

    public class gradients_application : MatchParentBoundsWidget
    {
        double center_x = 350;
        double center_y = 280;

        gamma_ctrl m_profile;
        spline_ctrl m_spline_r;
        spline_ctrl m_spline_g;
        spline_ctrl m_spline_b;
        spline_ctrl m_spline_a;
        UI.RadioButtonWidget m_GradTypeRBox;
        UI.RadioButtonWidget m_GradWrapRBox;

        double m_pdx;
        double m_pdy;

        class SaveData
        {
            internal double m_center_x;
            internal double m_center_y;
            internal double m_scale;
            internal double m_angle;
            internal double[] m_splineRArray = new double[10];
            internal double[] m_splineGArray = new double[10];
            internal double[] m_splineBArray = new double[10];
            internal double[] m_splineAArray = new double[10];
            internal double[] m_profileArray = new double[4];
        };

        SaveData m_SaveData = new SaveData();

        double m_prev_scale;
        double m_prev_angle;
        double m_scale_x;
        double m_prev_scale_x;
        double m_scale_y;
        double m_prev_scale_y;
        bool m_mouse_move;

        /*
        public virtual ~gradients_application()
        {
            FILE* fd = fopen(full_file_name("settings.dat"), "w");
            fprintf(fd, "%f\n", m_center_x);
            fprintf(fd, "%f\n", m_center_y);
            fprintf(fd, "%f\n", m_scale);
            fprintf(fd, "%f\n", m_angle);
            fprintf(fd, "%f\n", m_spline_r.x(0));
            fprintf(fd, "%f\n", m_spline_r.y(0));
            fprintf(fd, "%f\n", m_spline_r.x(1));
            fprintf(fd, "%f\n", m_spline_r.y(1));
            fprintf(fd, "%f\n", m_spline_r.x(2));
            fprintf(fd, "%f\n", m_spline_r.y(2));
            fprintf(fd, "%f\n", m_spline_r.x(3));
            fprintf(fd, "%f\n", m_spline_r.y(3));
            fprintf(fd, "%f\n", m_spline_r.x(4));
            fprintf(fd, "%f\n", m_spline_r.y(4));
            fprintf(fd, "%f\n", m_spline_r.x(5));
            fprintf(fd, "%f\n", m_spline_r.y(5));
            fprintf(fd, "%f\n", m_spline_g.x(0));
            fprintf(fd, "%f\n", m_spline_g.y(0));
            fprintf(fd, "%f\n", m_spline_g.x(1));
            fprintf(fd, "%f\n", m_spline_g.y(1));
            fprintf(fd, "%f\n", m_spline_g.x(2));
            fprintf(fd, "%f\n", m_spline_g.y(2));
            fprintf(fd, "%f\n", m_spline_g.x(3));
            fprintf(fd, "%f\n", m_spline_g.y(3));
            fprintf(fd, "%f\n", m_spline_g.x(4));
            fprintf(fd, "%f\n", m_spline_g.y(4));
            fprintf(fd, "%f\n", m_spline_g.x(5));
            fprintf(fd, "%f\n", m_spline_g.y(5));
            fprintf(fd, "%f\n", m_spline_b.x(0));
            fprintf(fd, "%f\n", m_spline_b.y(0));
            fprintf(fd, "%f\n", m_spline_b.x(1));
            fprintf(fd, "%f\n", m_spline_b.y(1));
            fprintf(fd, "%f\n", m_spline_b.x(2));
            fprintf(fd, "%f\n", m_spline_b.y(2));
            fprintf(fd, "%f\n", m_spline_b.x(3));
            fprintf(fd, "%f\n", m_spline_b.y(3));
            fprintf(fd, "%f\n", m_spline_b.x(4));
            fprintf(fd, "%f\n", m_spline_b.y(4));
            fprintf(fd, "%f\n", m_spline_b.x(5));
            fprintf(fd, "%f\n", m_spline_b.y(5));
            fprintf(fd, "%f\n", m_spline_a.x(0));
            fprintf(fd, "%f\n", m_spline_a.y(0));
            fprintf(fd, "%f\n", m_spline_a.x(1));
            fprintf(fd, "%f\n", m_spline_a.y(1));
            fprintf(fd, "%f\n", m_spline_a.x(2));
            fprintf(fd, "%f\n", m_spline_a.y(2));
            fprintf(fd, "%f\n", m_spline_a.x(3));
            fprintf(fd, "%f\n", m_spline_a.y(3));
            fprintf(fd, "%f\n", m_spline_a.x(4));
            fprintf(fd, "%f\n", m_spline_a.y(4));
            fprintf(fd, "%f\n", m_spline_a.x(5));
            fprintf(fd, "%f\n", m_spline_a.y(5));
            double x1,y1,x2,y2;
            m_profile.values(&x1, &y1, &x2, &y2);
            fprintf(fd, "%f\n", x1);
            fprintf(fd, "%f\n", y1);
            fprintf(fd, "%f\n", x2);
            fprintf(fd, "%f\n", y2);
            fclose(fd);
        }
         */

        gradients_application()
        {
            m_profile = new gamma_ctrl(10.0, 10.0, 200.0, 170.0-5.0);
            m_spline_r = new spline_ctrl(210, 10,     210+250, 5+40,  6);
            m_spline_g = new spline_ctrl(210, 10+40,  210+250, 5+80,  6);
            m_spline_b = new spline_ctrl(210, 10 + 80, 210 + 250, 5 + 120, 6);
            m_spline_a = new spline_ctrl(210, 10 + 120, 210 + 250, 5 + 160, 6);
            m_profile.MouseMove += new MouseMoveEventHandler(NeedRedraw);
            m_spline_r.MouseMove += new MouseMoveEventHandler(NeedRedraw);
            m_spline_g.MouseMove += new MouseMoveEventHandler(NeedRedraw);
            m_spline_b.MouseMove += new MouseMoveEventHandler(NeedRedraw);
            m_spline_a.MouseMove += new MouseMoveEventHandler(NeedRedraw);
            m_GradTypeRBox = new AGG.UI.RadioButtonWidget(10.0, 180.0, 200.0, 300.0);
            m_GradWrapRBox = new RadioButtonWidget(10, 310, 200, 375);

            m_pdx=(0.0);
            m_pdy=(0.0);
            m_SaveData.m_center_x = (center_x);
            m_SaveData.m_center_y = (center_y);
            m_SaveData.m_scale = (1.0);
            m_prev_scale=(1.0);
            m_SaveData.m_angle = (0.0);
            m_prev_angle=(0.0);
            m_scale_x=(1.0);
            m_prev_scale_x=(1.0);
            m_scale_y=(1.0);
            m_prev_scale_y=(1.0);
            m_mouse_move=(false);


            AddChild(m_profile);
            AddChild(m_spline_r);
            AddChild(m_spline_g);
            AddChild(m_spline_b);
            AddChild(m_spline_a);
            AddChild(m_GradTypeRBox);
            AddChild(m_GradWrapRBox);

            m_profile.border_width(2.0, 2.0);

            m_spline_r.background_color(new RGBA_Bytes(1.0, 0.8, 0.8));
            m_spline_g.background_color(new RGBA_Bytes(0.8, 1.0, 0.8));
            m_spline_b.background_color(new RGBA_Bytes(0.8, 0.8, 1.0));
            m_spline_a.background_color(new RGBA_Bytes(1.0, 1.0, 1.0));

            m_spline_r.border_width(1.0, 2.0);
            m_spline_g.border_width(1.0, 2.0);
            m_spline_b.border_width(1.0, 2.0);
            m_spline_a.border_width(1.0, 2.0);
            m_GradTypeRBox.border_width(2.0, 2.0);
            m_GradWrapRBox.border_width(2.0, 2.0);

            m_spline_r.point(0, 0.0,     1.0);
            m_spline_r.point(1, 1.0/5.0, 1.0 - 1.0/5.0);
            m_spline_r.point(2, 2.0/5.0, 1.0 - 2.0/5.0);
            m_spline_r.point(3, 3.0/5.0, 1.0 - 3.0/5.0);
            m_spline_r.point(4, 4.0/5.0, 1.0 - 4.0/5.0);
            m_spline_r.point(5, 1.0,     0.0);
            m_spline_r.update_spline();

            m_spline_g.point(0, 0.0,     1.0);
            m_spline_g.point(1, 1.0/5.0, 1.0 - 1.0/5.0);
            m_spline_g.point(2, 2.0/5.0, 1.0 - 2.0/5.0);
            m_spline_g.point(3, 3.0/5.0, 1.0 - 3.0/5.0);
            m_spline_g.point(4, 4.0/5.0, 1.0 - 4.0/5.0);
            m_spline_g.point(5, 1.0,     0.0);
            m_spline_g.update_spline();

            m_spline_b.point(0, 0.0,     1.0);
            m_spline_b.point(1, 1.0/5.0, 1.0 - 1.0/5.0);
            m_spline_b.point(2, 2.0/5.0, 1.0 - 2.0/5.0);
            m_spline_b.point(3, 3.0/5.0, 1.0 - 3.0/5.0);
            m_spline_b.point(4, 4.0/5.0, 1.0 - 4.0/5.0);
            m_spline_b.point(5, 1.0,     0.0);
            m_spline_b.update_spline();

            m_spline_a.point(0, 0.0,     1.0);
            m_spline_a.point(1, 1.0/5.0, 1.0);
            m_spline_a.point(2, 2.0/5.0, 1.0);
            m_spline_a.point(3, 3.0/5.0, 1.0);
            m_spline_a.point(4, 4.0/5.0, 1.0);
            m_spline_a.point(5, 1.0,     1.0);
            m_spline_a.update_spline();

            m_GradTypeRBox.add_item("Circular");
            m_GradTypeRBox.add_item("Diamond");
            m_GradTypeRBox.add_item("Linear");
            m_GradTypeRBox.add_item("XY");
            m_GradTypeRBox.add_item("sqrt(XY)");
            m_GradTypeRBox.add_item("Conic");
            m_GradTypeRBox.cur_item(0);

            m_GradWrapRBox.add_item("Reflect");
            m_GradWrapRBox.add_item("Repeat");
            m_GradWrapRBox.add_item("Clamp");
            m_GradWrapRBox.cur_item(0);

            /*
            FILE* fd = fopen(full_file_name("settings.dat"), "r");
            if(fd)
            {
                float x;
                float y;
                float x2;
                float y2;
                float t;

                fscanf(fd, "%f\n", &t); m_center_x = t;
                fscanf(fd, "%f\n", &t); m_center_y = t;
                fscanf(fd, "%f\n", &t); m_scale = t;
                fscanf(fd, "%f\n", &t); m_angle = t;
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_r.point(0, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_r.point(1, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_r.point(2, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_r.point(3, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_r.point(4, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_r.point(5, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_g.point(0, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_g.point(1, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_g.point(2, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_g.point(3, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_g.point(4, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_g.point(5, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_b.point(0, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_b.point(1, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_b.point(2, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_b.point(3, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_b.point(4, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_b.point(5, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_a.point(0, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_a.point(1, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_a.point(2, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_a.point(3, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_a.point(4, x, y);
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y); m_spline_a.point(5, x, y);
                m_spline_r.update_spline();
                m_spline_g.update_spline();
                m_spline_b.update_spline();
                m_spline_a.update_spline();
                fscanf(fd, "%f\n", &x);
                fscanf(fd, "%f\n", &y);
                fscanf(fd, "%f\n", &x2);
                fscanf(fd, "%f\n", &y2);
                m_profile.values(x, y, x2, y2);
                fclose(fd);
            }
            */
        }

        void NeedRedraw(object sender, MouseEventArgs mouseEvent)
        {
            Invalidate();
        }

        public override void OnDraw(Graphics2D graphics2D)
        {
            ScanlineRasterizer ras = new ScanlineRasterizer();
            scanline_unpacked_8 sl = new scanline_unpacked_8();

            ImageClippingProxy clippingProxy = new ImageClippingProxy(graphics2D.DestImage);
            clippingProxy.clear(new RGBA_Doubles(0, 0, 0));
          
            m_profile.text_size(8.0);

            //m_profile.Render(ras, sl, clippingProxy);
            //m_spline_r.Render(ras, sl, clippingProxy);
            //m_spline_g.Render(ras, sl, clippingProxy);
            //m_spline_b.Render(ras, sl, clippingProxy);
            //m_spline_a.Render(ras, sl, clippingProxy);
            //m_GradTypeRBox.Render(ras, sl, clippingProxy);
            //m_GradWrapRBox.Render(ras, sl, clippingProxy);

            // draw a background to show how the alpha is working
            int RectWidth = 32;
            int xoffset = 238;
            int yoffset = 171;
            for (int i = 0; i < 7; i++)
            {
                for (int j = 0; j < 7; j++)
                {
                    if ((i + j) % 2 != 0)
                    {
                        VertexSource.RoundedRect rect = new VertexSource.RoundedRect(i * RectWidth + xoffset, j * RectWidth + yoffset,
                            (i + 1) * RectWidth + xoffset, (j + 1) * RectWidth + yoffset, 2);
                        rect.normalize_radius();

                        ras.add_path(rect);
                        agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, ras, sl, new RGBA_Bytes(.9, .9, .9));
                    }
                }
            }

            double ini_scale = 1.0;

            Transform.Affine mtx1 = Affine.NewIdentity();
            mtx1 *= Affine.NewScaling(ini_scale, ini_scale);
            mtx1 *= Affine.NewTranslation(center_x, center_y);

            VertexSource.Ellipse e1 = new AGG.VertexSource.Ellipse();
            e1.init(0.0, 0.0, 110.0, 110.0, 64);

            Transform.Affine mtx_g1 = Affine.NewIdentity();
            mtx_g1 *= Affine.NewScaling(ini_scale, ini_scale);
            mtx_g1 *= Affine.NewScaling(m_SaveData.m_scale, m_SaveData.m_scale);
            mtx_g1 *= Affine.NewScaling(m_scale_x, m_scale_y);
            mtx_g1 *= Affine.NewRotation(m_SaveData.m_angle);
            mtx_g1 *= Affine.NewTranslation(m_SaveData.m_center_x, m_SaveData.m_center_y);
            mtx_g1.invert();


            RGBA_Bytes[] color_profile = new RGBA_Bytes[256]; // color_type is defined in pixel_formats.h
            for(int i = 0; i < 256; i++)
            {
                color_profile[i] = new RGBA_Bytes(m_spline_r.spline()[i], 
                                                        m_spline_g.spline()[i],
                                                        m_spline_b.spline()[i],
                                                        m_spline_a.spline()[i]);
            }

            VertexSourceApplyTransform t1 = new VertexSourceApplyTransform(e1, mtx1);

            IGradient innerGradient = null;
            switch(m_GradTypeRBox.cur_item())
            {
                case 0:
                    innerGradient = new gradient_radial();
                    break;

                case 1:
                    innerGradient = new gradient_diamond();
                    break;

                case 2:
                    innerGradient = new gradient_x();
                    break;

                case 3:
                    innerGradient = new gradient_xy();
                    break;

                case 4:
                    innerGradient = new gradient_sqrt_xy();
                    break;

                case 5:
                    innerGradient = new gradient_conic();
                    break;
            }

            IGradient outerGradient = null;
            switch (m_GradWrapRBox.cur_item())
            {
                case 0:
                    outerGradient = new gradient_reflect_adaptor(innerGradient);
                    break;

                case 1:
                    outerGradient = new gradient_repeat_adaptor(innerGradient);
                    break;

                case 2:
                    outerGradient = new gradient_clamp_adaptor(innerGradient);
                    break;
            }

            span_allocator span_alloc = new span_allocator();
            color_function_profile colors = new color_function_profile(color_profile, m_profile.gamma());
            span_interpolator_linear inter = new span_interpolator_linear(mtx_g1);
            span_gradient span_gen = new span_gradient(inter, outerGradient, colors, 0, 150);

            ras.add_path(t1);
            agg_renderer_scanline.Default.GenerateAndRender(ras, sl, clippingProxy, span_alloc, span_gen);
            base.OnDraw(graphics2D);
        }

        public override void OnMouseMove(MouseEventArgs mouseEvent)
        {
            if (m_mouse_move)
            {
                double x2 = mouseEvent.X;
                double y2 = mouseEvent.Y;

                /*
                if(flags & agg::kbd_ctrl)
                {
                    double dx = x2 - m_center_x;
                    double dy = y2 - m_center_y;
                    m_scale_x = m_prev_scale_x * dx / m_pdx;
                    m_scale_y = m_prev_scale_y * dy / m_pdy;
                    Invalidate();
                }
                else
                 */
                {
                    if (mouseEvent.Button == MouseButtons.Left)
                    {
                        m_SaveData.m_center_x = x2 + m_pdx;
                        m_SaveData.m_center_y = y2 + m_pdy;
                        Invalidate();
                    }

                    if (mouseEvent.Button == MouseButtons.Right)
                    {
                        double dx = x2 - m_SaveData.m_center_x;
                        double dy = y2 - m_SaveData.m_center_y;
                        m_SaveData.m_scale = m_prev_scale *
                                  System.Math.Sqrt(dx * dx + dy * dy) /
                                  System.Math.Sqrt(m_pdx * m_pdx + m_pdy * m_pdy);

                        m_SaveData.m_angle = m_prev_angle + System.Math.Atan2(dy, dx) - System.Math.Atan2(m_pdy, m_pdx);
                        Invalidate();
                    }
                }
            }

            base.OnMouseMove(mouseEvent);
        }

        public override void OnMouseDown(MouseEventArgs mouseEvent)
        {
            base.OnMouseDown(mouseEvent);

            if (MouseCaptured)
            {
                m_mouse_move = true;
                double x2 = mouseEvent.X;
                double y2 = mouseEvent.Y;

                m_pdx = m_SaveData.m_center_x - x2;
                m_pdy = m_SaveData.m_center_y - y2;
                m_prev_scale = m_SaveData.m_scale;
                m_prev_angle = m_SaveData.m_angle + System.Math.PI;
                m_prev_scale_x = m_scale_x;
                m_prev_scale_y = m_scale_y;
            }
            Invalidate();
        }


        public override void OnMouseUp(MouseEventArgs mouseEvent)
        {
            m_mouse_move = false;
            base.OnMouseUp(mouseEvent);
        }

        public override void OnKeyDown(KeyEventArgs keyEvent)
        {
            if(keyEvent.KeyCode == Keys.F1)
            {
                /*
                FILE* fd = fopen(full_file_name("colors.dat"), "w");
                int i;
                for(i = 0; i < 256; i++)
                {
                    color_type c = agg::rgba(m_spline_r.spline()[i], 
                                             m_spline_g.spline()[i],
                                             m_spline_b.spline()[i],
                                             m_spline_a.spline()[i]);
                    fprintf(fd, "    %3d, %3d, %3d, %3d,\n", c.r, c.g, c.b, c.a);
                }
                fclose(fd);

                fd = fopen(full_file_name("profile.dat"), "w");
                for(i = 0; i < 256; i++)
                {
                    fprintf(fd, "%3d, ", int(m_profile.gamma()[i]));
                    if((i & 0xF) == 0xF) fprintf(fd, "\n");
                }
                fclose(fd);
                 */
            }
            base.OnKeyDown(keyEvent);
        }

		public static void StartDemo()
		{
            GuiHalFactory.SetGuiBackend(GuiHalFactory.KnownGuiFactoriesIndexes.WindowsFormsBitmap);
            //GuiHalSurface primaryWindow = GuiHalFactory.CreatePrimarySurface(512, 400, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgr24);
            GuiHalSurface primaryWindow = GuiHalFactory.CreatePrimarySurface(512, 400, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgra32);

            primaryWindow.Caption = "AGG gradients with Mach bands compensation";

            primaryWindow.AddChild(new gradients_application());
            primaryWindow.Run();
		}

		[STAThread]
        public static void Main(string[] args)
        {
        	StartDemo();
        }
    };
}
